import regex as re
import os
import subprocess
from ruamel import yaml
from ruamel.yaml.emitter import Emitter
from autoconf_macro_parser import AutoconfMacroParser

Emitter.MAX_SIMPLE_KEY_LENGTH = 4096

class StringScanner:
    def __init__(self, input_str):
        self.input_str = input_str
        self.pos = 0

    def eos(self):
        return self.pos >= len(self.input_str)

    def peek(self, length):
        return self.input_str[self.pos:self.pos+length]

    def scan(self, pattern, flags=0):
        if flags==re.MULTILINE|re.DOTALL:
            match = re.match(pattern, self.input_str[self.pos:], re.VERBOSE|re.MULTILINE|re.DOTALL)
        elif flags==re.DOTALL:
            match = re.match(pattern, self.input_str[self.pos:], re.VERBOSE|re.DOTALL)
        elif flags==re.MULTILINE:
            match = re.match(pattern, self.input_str[self.pos:], re.VERBOSE|re.MULTILINE)
        else:
            match = re.match(pattern, self.input_str[self.pos:], re.VERBOSE)
        if match:
            self.pos += len(match.group(0))
            # print("111"+match.group(0))
            return match.group(0)
        else:
            return None

    def check(self, pattern, flags=0):
        if flags==re.MULTILINE|re.DOTALL:
            match = re.match(pattern, self.input_str[self.pos:], re.VERBOSE|re.MULTILINE|re.DOTALL)
        elif flags==re.DOTALL:
            match = re.match(pattern, self.input_str[self.pos:], re.VERBOSE|re.DOTALL)
        elif flags==re.MULTILINE:
            match = re.match(pattern, self.input_str[self.pos:], re.VERBOSE|re.MULTILINE)
        else:
            match = re.match(pattern, self.input_str[self.pos:], re.VERBOSE)
        if match:
            return match.group(0)
        else:
            return None

class AutoconfConditionParser(AutoconfMacroParser):
    def __init__(self):
        super().__init__()
        self.conditional_commands = []
        self.condition_indices = []
        self.current_conditions = []
        self.case_conditions = []
        self.last_conditional_code = None

    def parse_conditions(self):
        # self.cache_m4_macros()
        a = expand_if_case(self.m4_macros)
        self.parse_conditional_code(a)
        self.parse_conditional_code(self.configure_ac_content)

    def save_conditions(self,dir=None):
        if dir is None:
            dir = self.configure_ac_dir
        fn = os.path.join(dir, "conditions.yaml")
        with open(fn, 'w', encoding='utf-8') as file:
            yml = yaml.YAML()
            yml.dump(self.conditional_commands, file)
        return self.conditional_commands

    def parse_conditional_code(self, code):
        code = re.sub(r'^#+[ ]+.*$', '', code, flags=re.MULTILINE)  # Remove comment lines
        code = re.sub(r'^dnl[ ]+.*$', '', code, flags=re.MULTILINE)  # Remove configure.ac comments
        code = re.sub(r'[ \t]+#[ ]+.*$', '', code, flags=re.MULTILINE)  # Remove comments after whitespace
        code = re.sub(r'\\\n', '', code)  # Remove backslash newline quotes
        code = re.sub(r'&&\s*\n', '&& ', code)  # Replace && followed by newline with &&
        code = re.sub(r'\|\|\s*\n', '|| ', code)  # Replace || followed by newline with ||
        ac_re = r'''([A-Z_]+|m4_[a-z_]+)(?<paren_expression>
                  \(
                    (
                      [^()\[\]]             
                    |                       
                      (?<bracket_expression>
                       \[
                       ([^\[\]]*
                        |
                       (?&bracket_expression)
                       )*
                       \]
                      )
                    |                       
                      (?&paren_expression)  
                    )*
                  \)
                )'''
        ac_re2 = r'^([A-Z_]+|m4_[a-z_]+)\(\[(.*?)\]\)$'
        ac_re3 = r'^([A-Z_]+|m4_[a-z_]+)\((.*?)\)\R\R'
        scanner = StringScanner(code)
        while not scanner.eos():
            scanner.scan(r'\s+')
            scanner.scan(r'\t+')
            if if_cond := scanner.scan(r'if\s+(.*);\s*then'):
                self.parse_if_condition(if_cond)
                continue
            if if_cond := scanner.scan(r'if\s+(.*)\s*then'):
                self.parse_if_condition(if_cond)
                continue
            if elif_cond := scanner.scan(r'elif\s+(.*?);\s*then'):
                self.parse_elif_condition(elif_cond)
                continue
            if scanner.scan(r'else\b'):
                self.invert_last_condition()
                continue
            if scanner.scan(r'fi\b'):
                self.pop_conditions()
                if self.condition_indices:
                    self.condition_indices.pop()
                else:
                    print("Warning: Tried to pop from an empty condition_indices list.")
                continue
            if test_and := scanner.scan(r'test .* &&\s+[a-zA-Z_]+=.*$', re.MULTILINE):
                self.parse_test_cmd(test_and)
                continue
            if test_and := scanner.scan(r'test .* &&\s+AC_MSG_ERROR\(\[.*\]\)$', re.MULTILINE):
                self.parse_test_cmd(test_and)
                continue
            if test_and := scanner.scan(r'test .* &&\s+AC_MSG_WARN\(\[.*\]\)$', re.MULTILINE):
                self.parse_test_cmd(test_and)
                continue
            if case_in := scanner.scan(r'case\s+(.*?)\s+in(.*?)\)\]?\s', re.MULTILINE|re.DOTALL):
                self.parse_case_in(case_in)
                continue
            if scanner.scan(r';;\s+esac\s',re.MULTILINE|re.DOTALL):
                self.finish_case()
                continue
            if case_when := scanner.scan(r';;\s*(.*?)\)\s', re.MULTILINE|re.DOTALL):
                # print("111111 "+case_when)
                self.parse_case_when(case_when)
                continue
            if scanner.scan(r'esac\s*$', re.MULTILINE):
                self.finish_case()
                continue
            if ac_macro := scanner.scan(ac_re):
                # print("MACRO1: " + ac_macro)
                self.parse_command(ac_macro)
                continue
            if ac_macro := scanner.scan(ac_re2):
                # print("MACRO2: " + ac_macro)
                self.parse_command(ac_macro)
                continue
            if ac_macro := scanner.scan(ac_re3):
                # print("MACRO3: " + ac_macro)
                self.parse_command(ac_macro)
                continue
            
            if bracket := scanner.scan(r'^\[\S[^\]]+\]$', re.MULTILINE):
                # print("skipping bracket " + bracket)
                continue

            if pre_condition := scanner.scan(r'.*(&&|\|\|)'):
                # print("skipping command prefix " + pre_condition)
                continue

            if command := scanner.scan(r'[^\n]+'):
                # print("111 "+command)
                while True:
                    next_cmd = scanner.check(r'\s+[^\n]+', re.MULTILINE)
                    if not next_cmd:
                        break
                    if re.search(r'if|elif|else|fi|case|esac|;;|test|m4_[a-z_]+\(|[A-Z_]+\(', next_cmd):
                        break
                    scanner.pos += len(next_cmd)
                    command += "\n" + next_cmd.strip()
                self.parse_command(command.strip())
                continue
            else:
                scanner.scan(r';')

        return self.conditional_commands


    def parse_test_cmd(self, test_and):
        cond, cmd = test_and.split('&&')[:2]
        cond = normalize_condition(cond)
        self.current_conditions.append(cond)
        self.parse_command(cmd)
        self.pop_current_condition()

    def parse_if_condition(self, if_cond):
        condition = parse_condition(if_cond)
        self.last_conditional_code = if_cond
        self.condition_indices.append(len(self.current_conditions))
        self.current_conditions.append(condition)

    def parse_elif_condition(self, elif_cond):
        self.invert_last_condition()
        condition = parse_condition(elif_cond)
        self.current_conditions.append(condition)

    def parse_case_in(self, case_in):
        var, condition = re.search(r'case\s+(.*?)\s+in(.*?)\)\]?\s', case_in, re.MULTILINE|re.DOTALL).groups()
        var = quote_var(var)
        condition = condition.replace('\n','').replace('\t','')
        self.case_conditions.append({'var': var, 'when': []})
        self.condition_indices.append(len(self.current_conditions))
        self.last_conditional_code = case_in
        self.add_case_condition(condition)

    def parse_case_when(self, case_when):
        self.pop_conditions()
        condition = re.findall(r';;\s*(.*?)\)\s', case_when, re.MULTILINE|re.DOTALL)
        self.add_case_condition(condition[0])

    def finish_case(self):
        self.pop_conditions()
        self.condition_indices.pop()
        self.case_conditions.pop()

    def add_case_condition(self, condition):
        case_var = self.case_conditions[-1]['var']
        condition = condition.strip()

        if condition[0] == '[':
            condition = condition[1:]

        if '|' in condition:
            conditions = [f"test {case_var} = {val.strip()}" for val in condition.split('|')]
            self.case_conditions[-1]['when'].extend(conditions)
            condition = ' || '.join(conditions)
        elif condition == "*":
            condition = ' && '.join([c.replace(' = ', ' != ') for c in self.case_conditions[-1]['when']])
        else:
            condition = f"test {case_var} = {condition}"
            self.case_conditions[-1]['when'].append(condition)
        self.current_conditions.append(condition)

    def parse_command(self, command):
        if not self.current_conditions:
            return

        command = command.strip()
        if not command:
            return

        conditions = [condition.strip() for cond in self.current_conditions for condition in cond.split('&&') if
                      condition.strip()]
        conditions = reduce_conditions(conditions)
        self.conditional_commands.append({'command': command, 'conditions': conditions})

    def invert_last_condition(self):
        if not self.current_conditions:
            return

        last_condition = self.current_conditions[-1]
        last_condition = last_condition.replace('!=', 'TEMP').replace('=', '!=').replace('TEMP', '=')
        last_condition = last_condition.replace('&&', 'TEMP').replace('||', '&&').replace('TEMP', '||')
        last_condition = last_condition.replace('test -', 'TEMP').replace('test ! -', 'test -').replace('TEMP',
                                                                                                        'test ! -')
        self.current_conditions[-1] = last_condition

    def pop_current_condition(self):
        self.current_conditions.pop()

    def pop_conditions(self):
        if self.condition_indices == []:
            print(f"WARNING: Condition was empty, messed up code: {self.last_conditional_code}")
            return
        self.current_conditions = self.current_conditions[:self.condition_indices[-1]]

    def run_autoconf_multiline(self, macro):
        command = f"autoconf --trace={macro}:'$n:$l:$~@' {self.configure_ac_path}"
        result = subprocess.run(command, shell=True, capture_output=True, text=True)

        if result.returncode == 0:
            return result.stdout
        else:
            print("Error executing autoconf:")
            print(result.stderr)
            exit(1)


def quote_var(var):
    if var.endswith('"'):
        return var
    else:
        return f'"{var}"'


def normalize_condition(condition):
    # 删除末尾的分号
    condition = condition.strip(';')
    # 删除首尾空格
    condition = condition.strip()
    # 替换带方括号的模式为 'test \1'
    condition = re.sub(r'\[\s*(.*?)\s*\]', r'test \1', condition)
    # 替换 '-a' 为 ' && test '
    condition = condition.replace(' -a ', ' && test ')
    # 替换 '-o' 为 ' || test '
    condition = condition.replace(' -o ', ' || test ')

    # 用正则表达式分割条件，并进行处理
    parts = re.split(r'(\s+&&\s+|\s+\|\|\s+)', condition)
    normalized_parts = []
    for part in parts:
        part = part.strip()
        if part in ['&&', '||']:
            normalized_parts.append(part)
        elif re.match(r'^test\s+(.*)$', part):
            part = re.sub(r'^test\s+(.*)$', r'\1', part)
            if part.startswith('-z'):
                var = part.replace('-z', '').strip()
                normalized_parts.append(f'test {quote_var(var)} = ""')
            elif part.startswith('-n'):
                var = part.replace('-n', '').strip()
                normalized_parts.append(f'test {quote_var(var)} != ""')
            elif re.match(r'^(.*)\s+(!=|=|==)\s+(.*)$', part):
                var, op, val = re.match(r'^(.*)\s+(!=|=|==)\s+(.*)$', part).groups()
                op = op.replace('==', '=')
                if re.match(r'^"?x["$]', var) and re.match(r'^"?x', val):
                    var = var.replace('x', '')
                    val = val.replace('x', '')
                    if val == '':
                        val = '""' 
                normalized_parts.append(f'test {quote_var(var)} {op} {val}')
            else:
                normalized_parts.append('test ' + part)
        else:
            normalized_parts.append(part)

    return ' '.join(normalized_parts)


def reduce_conditions(conditions):
    or_conditions = [cond for cond in conditions if '||' in cond]
    single_conditions = [cond for cond in conditions if '||' not in cond]

    for single_condition in single_conditions:
        for index, or_condition in enumerate(or_conditions):
            or_subconditions = [subcond.strip() for subcond in or_condition.split('||')]

            if single_condition in or_subconditions:
                or_subconditions.remove(single_condition)
                or_conditions[index] = ' || '.join(or_subconditions)

    or_conditions = [cond for cond in or_conditions if cond.strip()]
    conditions = or_conditions + single_conditions

    conditions_to_remove = []
    for cond1 in conditions:
        if ' = ' in cond1:
            variable1 = cond1.split('=')[0].strip()
            for cond2 in conditions:
                if ' != ' in cond2:
                    variable2 = cond2.split('!=')[0].strip()
                    if variable1 == variable2:
                        conditions_to_remove.append(cond2)

    conditions = [cond for cond in conditions if cond not in conditions_to_remove]

    return conditions


def parse_condition(if_then):
    cond = re.sub(r'if|elif', '', if_then).replace('then', '').strip()
    cond = normalize_condition(cond)
    return cond


def expand_if_case(macros):
    output = []
    for m in macros:
        if m['macro_name'] == 'AS_IF':
            output.extend(AS_IF_to_shell(m['macro_params']))
        elif m['macro_name'] == 'AS_CASE':
            output.extend(AS_CASE_to_shell(m['macro_params']))
    return '\n'.join(output)


def AS_IF_to_shell(params):
    output = []
    output.append(f"if {params.pop(0)}; then")
    command = params.pop(0) if params else ':'
    output.append(command)

    while len(params) >= 2:
        condition = params.pop(0)
        command = params.pop(0) if params else ':'
        output.append(f"elif {condition}; then")
        output.append(command)

    if len(params) == 1:
        command = params.pop(0) if params else ''
        if command:
            output.append("else")
            output.append(command)

    output.append("fi")
    return output


def AS_CASE_to_shell(params):
    output = []
    output.append(f"case {params.pop(0)} in")

    while len(params) >= 2:
        condition = params.pop(0)
        command = params.pop(0) if params else ''
        output.append(f"\t{condition})")
        output.append(f"\t\t{command}")
        output.append(f"\t\t;;")

    if len(params) == 1:
        command = params.pop(0) if params else ''
        output.append("\t*)")
        output.append(f"\t\t{command}")
        output.append(f"\t\t;;")

    output.append("esac")
    return output

# if __name__=='__main__':
#     # Usage
#     parser = AutoconfConditionParser()
#     parser.load_configure_ac('code/autoconf_examples/bdwgc/configure.ac')
#     parser.parse_macros()
#     parser.parse_conditions()
